package clusteringAlg;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Size;

/**
 * @author Dominik
 *
 */
public class MySelfOrganizingMap extends SamplesSelector {

	// Mat subory pre jednoduchsiu pracu s obrazkami
	List<SOMNeuron> Neurons = new ArrayList<SOMNeuron>();
	ProcessImage stylePI;
	
	/**
	 * @return the stylePI
	 */
	public ProcessImage getStylePI() {
		return stylePI;
	}

	/**
	 * @param stylePI the stylePI to set
	 */
	public void setStylePI(ProcessImage stylePI) {
		stylePI = stylePI;
	}

	/**
	 * 
	 */
	public MySelfOrganizingMap() {
		super();

	}

	/**
	 * @param person
	 * @param nClusters
	 * @param iterations
	 * @param stylePI
	 */
	public MySelfOrganizingMap(Person person, int nClusters, int iterations,
			ProcessImage stylePI) {
		super(person, nClusters, iterations);
		this.stylePI = stylePI;
	}

	/**
	 * @param person
	 * @param nClusters
	 * @param iterations
	 */
	public MySelfOrganizingMap(Person person, int nClusters, int iterations) {
		super(person, nClusters, iterations);
		
	}
	
	public void setPositions(){
		
		if (nClusters == 0)
			return;
		
		int n = (int) Math.ceil(Math.sqrt(nClusters));
		//positions = new Point[nClusters];
		
		for (int i=0; i< n; i++){
			for (int j=0; j<n; j++){
				if (i*n+j < nClusters)
					this.Neurons.get(i*n+j).setPosition(new Point(i,j));
				else
					break;
			}
		}
	}
	
	//inicializacia algoritmu
	private void initAlg(){
		
		int n;
		Random r = new Random();
		//Help h=new Help(new Size(64,64));
			
		// vytvorenie neuronov s nahodnymi pociatocnymi hodnotami
		for(int i=0; i < this.nClusters; i++){
		
			n=r.nextInt(this.person.GetSampleCount()-1);
			Mat m = person.getSample(n).getImage().clone();
			
			SOMNeuron sample = new SOMNeuron(m, person.getSample(n).getPose());
			
			switch(this.getStylePI()){
			case GAUSS_BLUR:
				sample.BlurImage(new Size(r.nextInt(6)+6, r.nextInt(6)+6));
				break;
			case GAUSS_NOISE:
				sample.GaussianNoiseImage(0, 0.01);
				break;
			default:
				break;
			
			}
			
			sample.ScaleImage(0, 1);
			this.Neurons.add( sample );
			
		}
		
		this.setPositions();
		this.ScaleData(0, 1);

	}
	
	private double NeighborhoodFunction(double alfa, double sigma, int d, int es) {

		return (alfa * (Math.exp(-this.SquaredEukclideanNeuronDistance(d, es)/(2*Math.pow(sigma, 2)))));
		
	}
	
	/*
	 * stvorcova euklidovska vzdialenost medzi "poziciami neuronov"
	 * */
	
	private double SquaredEukclideanNeuronDistance(int i, int j){
		return Math.pow(this.Neurons.get(i).getPosition().x - this.Neurons.get(j).getPosition().x, 2) 
				+ Math.pow(this.Neurons.get(i).getPosition().y - this.Neurons.get(j).getPosition().y, 2) ;
		//return Math.pow((this.positions[i].x-this.positions[j].x), 2) + Math.pow((this.positions[i].y-this.positions[j].y), 2);
	}	
	
	private void Som(){
		
		double sigma, alfa, nf; 
		double[] distance = new double[this.nClusters];
		int indexMin=0;
		//Help h = new Help(new Size(64,64));
		
		/* inicializuje data : 
		*
		*	- nastavi nahodne vahy neuronom (vyberie realne fotografie a pomocou fcie blur ich rozmaze)
		*	- upravi sa interval hodnot pixelov z <-128,128> na <0,1>
		*/
		this.initAlg();
		//h.saveImages("D:/skola/diplomovka/vysledky/vstup", this.getSamples());
		/* Priebeh algoritmu : 
		 * 
		 * - nastavia sa hodnoty sigma a alfa
		 * - najdenie vitaza medzi neuronmi
		 * - urcenie vitazneho neuronu (okolie sa zmensuje kazdou dalsou iteraciou (hodnota sigma))
		 * - zmena vah vitazneho neuronu a jeho okolia
		 * - a spat na zaciatok cyklu
		 * 
		 */
		for (double i=0; i < this.iterations; i++ ){
			
			// - nastavia sa hodnoty sigma a alfa
			sigma = Math.exp(-(i/70)) * 2;
			alfa =  Math.exp(-(i/160));
			
			if (sigma < 0.004)
				sigma = 0.004;
			
			for (Sample s : this.person.getSamples()){
				
				//- najdenie vitaza medzi neuronmi
				for (int j=0; j<nClusters; j++){
					distance[j] = EukclideanDistance(this.Neurons.get(j).getScaledImage(), s.getScaledImage());
				}
				
				double min= Double.MAX_VALUE;
				
				for (int j=0; j<distance.length; j++){
					//System.out.println( distance[j] );
					if (min > distance[j]){
						min = distance[j];
						indexMin = j;
					}
				}

				for(int j=0; j<this.nClusters; j++){
					// - urcenie vitazneho neuronu (okolie sa zmensuje kazdou dalsou iteraciou (hodnota sigma))
					nf = this.NeighborhoodFunction(alfa, sigma, j, indexMin);
					
					this.Neurons.get(j).WeightUpdate(nf, s.getScaledImage());
					
					// - zmena vah vitazneho neuronu a jeho okolia
					//double[] array = this.WeightUpdate(nf, this.Neurons.get(j).getScaledImage(), s.getScaledImage());
					//this.Neurons.get(j).setScaledImage(array); 
				}

				
			}
			
			/*for (Sample m: this.Neurons){
				m.SetImageFromScaledImage(0, 255);
			}
			
			h.saveImages("D:/skola/diplomovka/vysledky/folder",(int) i, this.getSamples());
			// kontrolne vypisy
			System.out.println(i + ", " + err);*/
		}
		
		for (Sample m: this.Neurons){
			m.SetImageFromScaledImage(0, 255);
		}
		
		//h.saveImages("D:/skola/diplomovka/vysledky/folder",(int) i, this.getSamples());
		
	}
	
	public void Run(){
		
		this.Som();
		
	}

	@Override
	public List<Sample> GetMeanSamples() {

		return this.getSamples();
	}

	@Override
	public List<Sample> GetClosestSamples() {

		List<Sample> samples = this.getSamples();
		double min, dst;
		int counter = 0;
		
		for (Sample n: this.Neurons){
		
			min=Integer.MAX_VALUE;
			
			for (Sample m: this.person.getSamples()){
				dst = this.EukclideanDistance(m.getScaledImage(), n.getScaledImage());
				if (dst < min){
					samples.set(counter, m);
					min = dst;
				}
			}
			counter++;
		}
		
		return samples;
	}
	
	public List<Sample> getSamples(){
		List<Sample> samples = new ArrayList<Sample>();
		
		for (Sample s: this.Neurons){
			samples.add(s);
		}
		
		return samples;
	}
	
}
